/*
 * Copyright 2012 Quytelda Gaiwin
 *
 *	Licensed under the Apache License, Version 2.0 (the "License");
 *	you may not use this file except in compliance with the License.
 *	You may obtain a copy of the License at
 *
 *		http://www.apache.org/licenses/LICENSE-2.0
 *
 *	Unless required by applicable law or agreed to in writing, software
 *	distributed under the License is distributed on an "AS IS" BASIS,
 *	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *	See the License for the specific language governing permissions and
 *	limitations under the License.
 */

/*
 * Panther.java
 * Created on Nov 30, 2008, 11:49:18 AM
 */
package org.tamalin.panthersleek;

import javax.crypto.*;
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.*;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.SecretKeyEntry;
import java.security.cert.CertificateException;
import java.util.*;
import java.util.logging.*;
import java.lang.reflect.*;
import com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel;

/**
 * The main window of Panther Sleek. Displays the main window, loads the properties,
 * stores the keys, and holds the password.
 *
 * @author Quytelda K. Gaiwin
 * @since 1.0.0
 */
public class Panther extends JFrame
{
	/**
	 * This is the start method, which gets everything up and running in the main window of the program.
	 *
	 * @param args The command line arguments
	 */
	public Panther(String calg, String dalg)
	{
		/************************
		* Program Configuration *
		*************************/
		Logger.getLogger(Panther.class.getName()).log(Level.INFO, "Loading configuration.");
		this.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		
		//Load keystore
		try
		{
			keystore = KeyStore.getInstance("JCEKS");
			this.loadKeys();
		}
		catch(Exception ex)
		{
			Logger.getLogger(Panther.class.getName()).log(Level.SEVERE, null, ex);
		}

		/******************
		* Interface Setup *
		*******************/
		Logger.getLogger(Panther.class.getName()).log(Level.INFO, "Setting up GUI");
		this.setTitle("Panther Sleek");
		URL imageURL = Panther.class.getResource("/org/tamalin/panthersleek/resources/dock-logo.png");
		Image img = Toolkit.getDefaultToolkit().getImage(imageURL);
		setIconImage(img);
		
		initComponents();
		this.setSize(500, 500);

		// Set up event handlers for each interface component

		// Use the native LAF
		String systemLAF = UIManager.getSystemLookAndFeelClassName();
		String crossLAF = UIManager.getCrossPlatformLookAndFeelClassName();
	}


	/*####################
	*# Interface Methods #
	*#####################*/
	
	/**
	 * This method is called from within the constructor to
	 * initialize the form.
	 */
	@SuppressWarnings("unchecked")
	private void initComponents()
	{
		if (System.getProperty("os.name").equals("Mac OS X"))
		{
			this.getRootPane().putClientProperty("apple.awt.brushMetalLook", Boolean.TRUE);
		}
		
		/**********************************
		 * Load localization information. *
		 **********************************/
		Logger.getLogger(Panther.class.getName()).log(Level.INFO, "Loading localization information");

		/**********************************
		* Instatiate interface components *
		***********************************/
		Logger.getLogger(Panther.class.getName()).log(Level.INFO, "Instantiating interface components");
		north = new JPanel();
		key = new JComboBox();
		keyLabel = new JLabel("Key:");
		plaintext = new JTextArea(25, 30);
		mb = new JMenuBar();
		
		this.setLayout(new BorderLayout());
		this.add(plaintext, BorderLayout.CENTER);
		north.add(keyLabel);
		north.add(key);
		this.add(north, BorderLayout.NORTH);
		
		/*********************
		* Configure menu bar *
		**********************/
		file = new JMenu("File");
		text = new JMenu("Text");
		edit = new JMenu("Edit");
		help = new JMenu("Help");
		
		for(JMenu menu : new JMenu[] {file, text, edit, help})
			mb.add(menu);
		
		encryptHD = file.add("Encrypt HD File...");
		decryptHD = file.add("Decrypt HD File...");
		digestHD = file.add("Digest HD File...");
		file.addSeparator();
		exit = file.add("Exit");
		
		encrypt = text.add("Save as Ciphertext...");
		decrypt = text.add("Open from Ciphertext...");
		text.addSeparator(); 
		save = text.add("Save as Plaintext...");
		open = text.add("Open from Plaintext...");
		
		changePassword = edit.add("Change Password...");
		preferences = edit.add("Preferences...");
		
		about = help.add("About...");

		encryptHD.setAccelerator(KeyStroke.getKeyStroke(new Character('E'), Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() + InputEvent.SHIFT_DOWN_MASK));
		decryptHD.setAccelerator(KeyStroke.getKeyStroke(new Character('D'), Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() + InputEvent.SHIFT_DOWN_MASK));
		digestHD.setAccelerator(KeyStroke.getKeyStroke(new Character('F'), Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() + InputEvent.SHIFT_DOWN_MASK));
		encrypt.setAccelerator(KeyStroke.getKeyStroke(new Character('E'), Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
		decrypt.setAccelerator(KeyStroke.getKeyStroke(new Character('D'), Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
		save.setAccelerator(KeyStroke.getKeyStroke(new Character('S'), Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() + InputEvent.SHIFT_DOWN_MASK));
		open.setAccelerator(KeyStroke.getKeyStroke(new Character('O'), Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() + InputEvent.SHIFT_DOWN_MASK));
		
		about.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent evt)
			{
				Panther.this.showAbout();
			}
		});
		
		changePassword.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent evt)
			{
				Panther.this.changePassword();
			}
		});
		
		open.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent evt)
			{
				// open plaintext file
			}
		});
		
		this.setJMenuBar(mb);
		pack();
	}
	
	/**
	* Initializes a new installation by creating all the necessary infrastructure, such as configuration files and the keystore file.
	*/
	private void initialize()
	{
		Logger.getLogger(Panther.class.getName()).info("Initializing new installation.");
		try
		{
			// create configuration directory
			File confDir = new File(CONF_DIR);
			if(!confDir.exists()) confDir.mkdirs();
			
			File dataDir = new File(DATA_DIR);
			if(!dataDir.exists()) dataDir.mkdirs();

			keystore.load(null, password);
			saveKeys();
		}
		catch(Exception ex)
		{
			Logger.getLogger(Panther.class.getName()).log(Level.SEVERE, "Failed to initialize JCEKS KeyStore.", ex);
		}		
	}
	
	/**
	* This method changes the Look and Feel of the program to the specified Look and Feel class name.
	* @param lafc the name of the LAF class to use for the program's Look and Feel
	*/
	private void setLookAndFeel(String lafc)
	{
		// Try to set the look and feel to whatever in specified in the preferences, if any.
		try
		{
			Logger.getLogger(Panther.class.getName()).log(Level.INFO, "Applying LAF: {0}", lafc);
			UIManager.setLookAndFeel(lafc);
			SwingUtilities.updateComponentTreeUI(this);
		}
		catch (Exception ex)
		{
			Logger.getLogger(Panther.class.getName()).log(Level.WARNING, null, ex);
		}
	}

	/*###########################################
	*# Class Getter/Setter & Properties Methods #
	*############################################*/
	public String getCipherAlgorithm()
	{
		return cipherAlgorithm;
	}
	
	public void setCipherAlgorithm(String algorithm)
	{
		cipherAlgorithm = algorithm;
	}
	
	public String getDigestAlgorithm()
	{
		return digestAlgorithm;
	}
	
	public void setDigestAlgorithm(String algorithm)
	{
		digestAlgorithm = algorithm;
	}

	/*##################
	*# Utility methods #
	*###################*/
	private void showAbout()
	{
		AboutDialog dialog = new AboutDialog(this, false);
		dialog.pack();
		dialog.setVisible(true);
	}
	
	/**
	* Loads the keys from the encrypted keystore file.
	*/
	private void loadKeys() throws IOException, GeneralSecurityException
	{
		// Get the keystore password	
		PasswordPrompt pp = new PasswordPrompt(null, true, "Please enter the master password:");
		pp.setVisible(true);
		password = pp.getPassword();
		
		File file = new File(DATA_DIR + "/keystore");
		
		if(password == null)
		{
			Logger.getLogger(Panther.class.getName()).info("Null password; exiting...");
			System.exit(0);
		}
		
		if(!file.exists()) this.initialize();
		
		try
		{
			FileInputStream fis = new FileInputStream(file);
			keystore.load(fis, password);
		}
		catch(IOException ex)
		{
			Logger.getLogger(Panther.class.getName()).log(Level.SEVERE, null, ex);
		}
		catch(GeneralSecurityException ex)
		{
			Logger.getLogger(Panther.class.getName()).log(Level.SEVERE, null, ex);
		}
	}
	
	/**
	* Saves the contents of the keystore in memory to the keystore on the disk.
	*/
	private void saveKeys() throws IOException, GeneralSecurityException
	{
		Logger.getLogger(Panther.class.getName()).log(Level.INFO, "Saving keys");
		File file = new File(DATA_DIR + "/keystore");
		System.out.println(file.getPath());
		System.out.println(": " + file.exists());
		
		FileOutputStream fos = new FileOutputStream(file);
		keystore.store(fos, password);
	}
	
	/**
	* Prompts for a new password, and checks it for validity.
	* If the password is valid it is set as the new global program password.
	*/
	private void changePassword()
	{
		char[] pswd, pswd2;
		// get new password
		PasswordPrompt pp = new PasswordPrompt(this, true, "Please enter new password:");
		pp.setVisible(true);
		pswd = pp.getPassword();

		// get confirmation password
		pp.setMessage("Please confirm password:");
		pp.setVisible(true);
		pswd2 = pp.getPassword();
		pp.cleanUp();

		// check password for validity
		// then utilize new password
		if(checkPswd(pswd, pswd2)) password = pswd;
		
		// overwrite password arrays when the method finishes
		for(int i = 0; i < pswd.length; i++) pswd[i] = 0;
		for(int i = 0; i < pswd2.length; i++)pswd2[i] = 0;
	}

	/**
	* Checks a password against its confirmation password to ensure that:
	* (a) they are equivalent, to make sure it has been entered correctly
	* (b) the password is not null, as null passwords are insecure and defeat the purpose of using encryption software
	*
	* @param pswd1 the password to be checked for validity and accuracy
	* @param pswd2 the confirmation password (ideally identical to pswd1)
	*
	* @return a boolean value indicating if the password is valid and matches it's confirmation password (true)
	*/
	private boolean checkPswd(char[] pswd1, char[] pswd2)
	{
		if(!Arrays.equals(pswd1, pswd2))
		{
			JOptionPane.showMessageDialog(null, "Passwords do not match!", "Password Mismatch", JOptionPane.ERROR_MESSAGE);
			return false;
		}
		else if(pswd1 == null)
		{
			JOptionPane.showMessageDialog(null, "Null passwords are not allowed.", "Null Password", JOptionPane.ERROR_MESSAGE);
			return false;
		}

		return true;
	}

	/**
	* cleans up the program environment when the program is closed by overwriting sensitive information
	*/
	private void cleanUp()
	{
		// overwrite the password in memory with zeros
		for(int i = 0; i < password.length; i++)
			password[i] = 0;
	}

	/***********************
	* Interface Components *
	************************/
	JPanel north;
	JTextArea plaintext;
	JComboBox key;
	JLabel keyLabel;
	JMenuBar mb;
	JMenu file, text, edit, help;
	JMenuItem decryptHD, encryptHD, digestHD, exit;
	JMenuItem encrypt, decrypt, open, save, digest;
	JMenuItem changePassword, preferences, about;

	/******************************
	* Program instance properties *
	*******************************/
	private String cipherAlgorithm = "AES", digestAlgorithm = "SHA-1";
	private char[] password;
	private KeyStore keystore = null;

	/***************************
	* Global Program Constants *
	****************************/
	public static final String VERSION = "3.0 (unstable)";
	public static final String BUNDLE_PATH = "org.tamalin.panthersleek.resources.international.panthersleek";
	public static final String ERROR_BUNDLE_PATH = "org.tamalin.panthersleek.resources.international.errors";
	public static final String CONF_DIR = (System.getProperty("os.name").equals("Linux")) ? "/etc/panthersleek" : (System.getProperty("os.name").equals("Mac OS X")) ? "/Users/matthew/Library/Preferences/org.tamalin.panthersleek" : System.getProperty("java.class.path");
	public static final String DATA_DIR = (System.getProperty("os.name").equals("Linux")) ? "/usr/share/panthersleek" : (System.getProperty("os.name").equals("Mac OS X")) ? "/Users/matthew/Library/Application Support/org.tamalin.panthersleek" : System.getProperty("java.class.path");
}
